package nimbus.commands
{
	
	/**
	 * The CommandHistory executes and stores undoable commands as a history
	 * allowing undo and redo, limiting the level of undo's, etc.
	 */
	[Bindable]
	public class CommandHistory
	{
		/**
		 * The actual list of commands, in order of occurance. This list should
		 * not be manipultated directly and should be treated as read only.
		 */
		public var history:Array;
		
		/**
		 * The current command in the history, often the last command executed.
		 */
		public var currentCommand:IUndoableCommand;
		
		/**
		 * Specifies whether there are commands availble to undo
		 */
		public var canUndo:Boolean = false;
		
		/**
		 * Specifies whether there are commands availble to redo
		 */
		public var canRedo:Boolean = false;
		
		private var _presentPosition:int = 0;			// the internal position of the history at the present time.
		private var _undoLevel:uint = 100;				// the internal level of undo's allowed.
		private var combiningCommand:ICommand;			// the last command executed with combine
		
		public function CommandHistory()
		{
			clearHistory();
		}
		
		/**
		 * The internal position of the history at the present time.
		 */
		public function get presentPosition():int
		{
			return _presentPosition;
		}
		public function set presentPosition(value:int):void
		{
			_presentPosition = value;
			
			if(_presentPosition > undoLevel)
			{
				history.splice(0, _presentPosition - undoLevel);
				_presentPosition = undoLevel;
			}
			
			currentCommand = history[_presentPosition-1] as IUndoableCommand;
			canUndo = Boolean(_presentPosition > 0);
			canRedo = Boolean(_presentPosition < history.length);
		}
		
		/**
		 * The number of undoable commands that will be stored in the history. Older commands
		 * will be removed from history and made eligble for garbage collection as this limit is exceeded.
		 */
		public function get undoLevel():uint
		{
			return _undoLevel;
		}
		public function set undoLevel(value:uint):void
		{
			_undoLevel = value;
			
			if(presentPosition > undoLevel)
			{
				history.splice(0, presentPosition - undoLevel);
				presentPosition = undoLevel;
			}
		}
		
		/**
		 * Adds a command to the history before executing it. This is how all commands should
		 * be first introduced to the CommandHistory to ensure undo can rely on an initial execution.
		 */
		public function execCommand(command:IUndoableCommand, combine:Boolean = false):void
		{
			combine = Boolean(combine && command is ICombinableCommand)
			if(combine && combiningCommand is Object(command).constructor)
			{
				ICombinableCommand(combiningCommand).combineCommand(command as ICombinableCommand);
				return;
			}
			combiningCommand = combine ? command : null;
			
			if(history.indexOf(command) == -1)
			{
				history.splice(presentPosition, history.length, command);
				presentPosition++;
			}
			
			command.execute();
		}
		
		/**
		 * Reverses the action of the last command executed.
		 */
		public function undo():Boolean
		{
			if(!canUndo)
				return false;
			
			combiningCommand = null;
			
			var command:IUndoableCommand = history[presentPosition-1] as IUndoableCommand;
			presentPosition--;
			
			command.undo();
			return true;
		}
		
		/**
		 * Re-executes the next command in the history.
		 */
		public function redo():Boolean
		{
			if(!canRedo)
				return false;
			
			var command:IUndoableCommand = history[presentPosition] as IUndoableCommand;
			presentPosition++;
			
			command.execute();
			return true;
		}
		
		/**
		 * Clears the current command from being combined with the next.
		 */
		public function resetCombine():void
		{
			combiningCommand = null;
		}
		
		/**
		 * Clears all commands from the history and resets the present position to zero.
		 */
		public function clearHistory():void
		{
			history = [];
			presentPosition = 0;
			combiningCommand = null;
		}
		
	}
}
